/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.application.compilation;

import static com.softwarecraftsmen.orogeny.application.compilation.ClassToResourceUrl.toExtantResourceUrl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import static javax.tools.JavaFileObject.Kind.CLASS;
import javax.tools.StandardJavaFileManager;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import static java.util.Arrays.asList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class InMemoryFileManager extends ForwardingJavaFileManager<StandardJavaFileManager>
{
	private final ClassLoader parentClassLoader;
	private final List<CompiledClassRecord> compiledClassRecords;
	private final StandardJavaFileManager javaFileManager;

	public InMemoryFileManager(final @NotNull StandardJavaFileManager javaFileManager)
	{
		super(javaFileManager);
		this.javaFileManager = javaFileManager;
		parentClassLoader = getClass().getClassLoader();
		compiledClassRecords = new ArrayList<CompiledClassRecord>();
	}

	@NotNull
	public InMemoryCompiledClassLoader getCompiledClassLoader()
	{
		final Map<String, byte[]> classNameToBytes = new LinkedHashMap<String, byte[]>();
		for (CompiledClassRecord compiledClassRecord : compiledClassRecords)
		{
			compiledClassRecord.storeClassBytes(classNameToBytes);
		}
		return new InMemoryCompiledClassLoader(parentClassLoader, classNameToBytes);
	}

	@Override
	public JavaFileObject getJavaFileForInput(final @Nullable JavaFileManager.Location location, final @NotNull String className, final @NotNull Kind kind) throws IOException
	{
		if (kind == CLASS)
		{
			final URL resourceUrl = toExtantResourceUrl(className, parentClassLoader);
			if (resourceUrl != null)
			{
				return new UrlJavaFileObject(resourceUrl);
			}
		}
		return super.getJavaFileForInput(location, className, kind);
	}

	@Override
	public JavaFileObject getJavaFileForOutput(final @Nullable JavaFileManager.Location location, final @NotNull String className, final @NotNull Kind kind, final @Nullable FileObject sibling) throws IOException
	{
		if (kind == CLASS)
		{
			final CompiledClassRecord compiledClassRecord = new CompiledClassRecord(className);
			compiledClassRecords.add(compiledClassRecord);
			return compiledClassRecord;
		}
		else
		{
			return super.getJavaFileForOutput(location, className, kind, sibling);
		}
	}

	@Override
	public Iterable<JavaFileObject> list(final @Nullable JavaFileManager.Location location, final @NotNull String packageName, final @NotNull Set<Kind> kinds, final boolean recurse) throws IOException
	{
		final List<JavaFileObject> results = new LinkedList<JavaFileObject>();
		for (Object o : super.list(location, packageName, kinds, recurse))
		{
			results.add((JavaFileObject) o);
		}

		final String prefixedPackageName = isDefaultPackage(packageName) ? packageName : packageName + ".";
		for (CompiledClassRecord compiledClassRecord : compiledClassRecords)
		{
			final String className = compiledClassRecord.toDotSeparatedFullyQualifiedClassNameWithoutDotClassExtension();
			if (isDefaultPackage(prefixedPackageName) && isInDefaultPackage(className))
			{
				results.add(compiledClassRecord);
			}
			else if (isInPackageOrSubpackage(prefixedPackageName, className) && isInPackage(prefixedPackageName, className))
			{
				results.add(compiledClassRecord);
			}
		}
		return results;
	}

	private boolean isInPackageOrSubpackage(final String prefixedPackageName, final String className)
	{
		return className.startsWith(prefixedPackageName);
	}

	private boolean isInPackage(final String prefixedPackageName, final String className)
	{
		return nameHasNoPeriod(stripPrefixedPackageName(prefixedPackageName, className));
	}

	private boolean isDefaultPackage(final String prefixedPackageName)
	{
		return prefixedPackageName.isEmpty();
	}

	private boolean isInDefaultPackage(final String className)
	{
		return nameHasNoPeriod(className);
	}

	private String stripPrefixedPackageName(final String prefixedPackageName, final String className)
	{
		return className.substring(prefixedPackageName.length());
	}

	private boolean nameHasNoPeriod(final String name)
	{
		return !name.contains(".");
	}

	@Override
	public String inferBinaryName(Location location, JavaFileObject file)
	{
		if (file instanceof CompiledClassRecord)
		{
			return ((CompiledClassRecord) file).getClassName();
		}
		return super.inferBinaryName(location, file);
	}

	@NotNull
	public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(final @NotNull File... script)
	{
		return javaFileManager.getJavaFileObjectsFromFiles(asList(script));
	}
}
